package com.wyw.zklock.learning.lock;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * @Title   Zookeeper分布式锁
 * @Description
 * Zookeeper观察器原理
 * 可以观察zookeeper的三个方法：getData(),getChildren(),exists()方法
 * 节点数据发生变化，发送给客户端
 * 观察器只能观察一次，再监控需要重新设置
 *
 * 实现原理：
 * 利用zookeeper的是瞬时有序节点的特性（注意瞬时节点下不能再有节点，防止释放导致后续节点出问题）
 * 多线程并发创建瞬时节点时得到有序的序列
 * 序号最小的获得锁
 * @Author Mr Wu yewen.wu.china@gmail.com
 * @Date 2024/3/29 10:12
 * Update History:
 * Author        Time            Content
 */
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {

    private final static String basePath = "/";

    private ZooKeeper zooKeeper;
    private String businessName;
    private String znode;

    public ZkLock(String connectString, String businessName) throws IOException {
        //1、地址+端口，2、超时时间，3、watcher，因为本类已经实现了Watcher，所以直接使用本身就可以了
        this.zooKeeper = new ZooKeeper(connectString, 30000, this);//因为ZkLock本身已经实现监听方法，所以可以直接使用本类
        this.businessName = businessName;
    }

    public boolean getLock() {
        //1、创建业务的根节点
        try {
            Stat existsNode = zooKeeper.exists("/" + businessName, false);
            if(existsNode == null){
                zooKeeper.create("/" + businessName, businessName.getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);//为什么要用持久类型，这样Zookeeper挂了也存在，与业务有关，锁无关
            }
            //创建瞬时有序节点  例：/order/order_00000001
            znode = zooKeeper.create("/" + businessName + "/" + businessName + "_", businessName.getBytes(),
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);//瞬时节点

            //获取业务节点下的所有子节点
            List<String> childrenNodes = zooKeeper.getChildren("/" + businessName, false);
            //子节点进行排序
            Collections.sort(childrenNodes);
            //获取序号最小的一个子节点
            String firstNode = childrenNodes.get(0);
            log.info("123：{}", firstNode);
            //如果创建的节点是第一个节点，则获得锁
            if(znode.endsWith(firstNode)){
                return true;
            }
            //不是第一个节点，则监听上一个节点
            String lastNode = firstNode;
            for (String node:childrenNodes){
                if (znode.endsWith(node)){
                    zooKeeper.exists("/" + businessName + "/" + lastNode,true);
                    break;
                } else {
                    lastNode = node;
                }
            }
            synchronized (this){
                wait();
            }
            return true;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }


    @Override
    public void process(WatchedEvent watchedEvent) {
        if(watchedEvent.getType() == Event.EventType.NodeDeleted){
            synchronized (this){
                notify();
            }
        }
    }

    @Override
    public void close() throws Exception {
        zooKeeper.delete( znode, -1);
        zooKeeper.close();
        log.info("释放锁");
    }
}

